package adraw;

import geometry.*;


import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


import javax.swing.*;

/**
 * Copyright 2008 Viera K. Proulx
 * This program is distributed under the terms of the 
 * GNU Lesser General Public License (LGPL)
 */

/**
 * <P>World for programming interactive games - with graphics, key events,
 * mouse events and a timer. Designed to implement the same interface as
 * DrScheme/ProfessorJ world teachpack library.</P>
 * <P>Adapted for the use within a Java applet.</P>
 * 
 * @author Viera K. Proulx
 * @since November 15, 2007
 */
abstract public class World{

  /** the canvas that displays the current world */
  protected Canvas theCanvas;

  /** true if 'bigBang' started the world and it did not end, did not stop */
  protected boolean worldExists = false;

  /** true if 'bigBang' started the world and the applet stopped the world */
  protected boolean worldStopped = false;

  /** the timer for this world */
  protected MyTimer mytime;

  /** the key adapter for this world */
  protected MyKeyAdapter ka;
  
  /** the button to set to CLEAR when the world ends */
  protected JButton bigButton;
   
  /**
   * The default constructor. To start the world one must invoke the
   * <code>bigBang</code> method.
   */
  public World(){ }

  /////////////////////////////////////////////////////////////////////////
  // Methods for interacting with the World                              //
  /////////////////////////////////////////////////////////////////////////
  /**
   * Initialize the world with the canvas, its key adapter, 
   * and mouse adapter.
   */
  protected void initWorld(Canvas theCanvas, 
                        JButton bigButton){
    this.theCanvas = theCanvas;
    this.bigButton = bigButton;
    
  }
 
  /**
   * Start the world by creating a canvas of the given size, creating
   * and adding the key and mouse adapters, and starting the timer at the
   * given speed.
   * 
   * @param w the width of the <code>{@link Canvas Canvas}</code>
   * @param h the height of the <code>{@link Canvas Canvas}</code>
   * @param speed the speed at which the clock runs
   */
  public void bigBang(int w, int h, double speed){
 
    // record that the world is running 
    this.worldExists = true;

    // set the drawing size to the given width and height
    this.theCanvas.painter.setSize(w, h);
    
    // add a new key listener to the frame for our canvas
    this.ka = new MyKeyAdapter(this);
    this.theCanvas.painter.addKeyListener(ka);

    // make sure the canvas can respond to the key events
    this.theCanvas.setFocusable(true);

    // add the timer -- start it if speed is greater than 0
    this.mytime = new MyTimer(this, speed);
    if (speed > 0.0)
      this.mytime.timer.start();

    // finally, show the canvas and draw the initial world
    this.theCanvas.addNotify(); 
    this.theCanvas.setVisible(true);
    this.drawWorld();
    
    // note that the applet has not been stopped while the world runs
    this.worldStopped = false;
  }


  /**
   * Stop the timer, disable event processing - leave the canvas open, 
   * write the end of time message onto the canvas
   * 
   * @param s the message to display
   */
  public void endOfTime(String s){
    // remove listeners and set worldExists to false
    this.mytime.timer.stop();
    this.worldExists = false;
    this.worldStopped = true;
    this.theCanvas.painter.removeKeyListener(this.ka);
    System.out.println("The world stopped: " + s);

    // draw the final scene of the world with the end of time message
    this.theCanvas.clearPanel();
    this.draw();
    this.theCanvas.drawString(new Posn(10, 20),s);
    
    // 'hit the STOP button' in the applet controls
    this.bigButton.setText(WorldApplet.CLEAR);
  }


  /**
   * End the world interactions - leave the canvas open, 
   * write the end of world message onto the canvas
   * 
   * @param s the message to display
   * @return <code>this</code> world
   */
  public World endOfWorld(String s){
    // remove listeners and set worldExists to false
    this.mytime.timer.stop();
    this.worldExists = false;
    this.worldStopped = true;
    this.theCanvas.painter.removeKeyListener(this.ka);
    System.out.println("The end of the world: " + s);

    // draw the final scene of the world with the end of world message
    this.theCanvas.clearPanel();
    this.draw();
    this.theCanvas.drawString(new Posn(10, 20),s);
    
    // 'hit the STOP button' in the applet controls
    this.bigButton.setText(WorldApplet.CLEAR);
    
    return this;
  }

  /**
   * The method invoked by the timer on each tick.
   * Delegates to the user to define a new state of the world,
   * then resets the canvas and event handlers for the new world
   * to those currently used.
   * 
   * @return <code>{@link World World}</code> after the tick event
   */
  protected void processTick(){
    try{
      if (this.worldExists){
        System.out.println("Tick");
        this.onTick();
        this.drawWorld();
      }
    } 
    catch(RuntimeException re){
      re.printStackTrace();
      this.endOfTime(re.getMessage());
      //throw re;
      Runtime.getRuntime().halt(1);
    }
  }  

  /**
   * <P>User defined method to be invoked by the timer on each tick.
   * Updates the <code>{@link World World}</code>.</P>
   * <P>Override this method in the game world class</P>
   */
  abstract public void onTick();
    

  /**
   * The method invoked by the key adapter on selected key events.
   * Delegates to the user to define a new state of the world,
   * then resets the canvas and event handlers for the new world
   * to those currently used.
   */
  protected void processKeyEvent(String ke){
    try{
      if (worldExists){

        System.out.println("Key event " + ke);
        this.onKeyEvent(ke);
        this.drawWorld();
      }
    }
    catch(RuntimeException re){
      re.printStackTrace();
      this.endOfTime(re.getMessage());
      //throw re;
      Runtime.getRuntime().halt(1);
    }
  }

  /**
   * <P>User defined method to be invoked by the key adapter 
   * on selected key events.
   * Updates the new <code>{@link World World}</code>.</P>
   * <P>Override this method in the game world class</P>
   */
  abstract public void onKeyEvent(String s);

  /**
   * Invoke the user defined <code>draw</code> method, if this 
   * <code>{@link World World}</code> has been initialized 
   * via <code>bigBang</code> and did not stop or end 
   * via <code>endOfTime</code> 
   * or <code>endOfWorld</code>
   * 
   * @return <code>true</code>
   */
  protected void drawWorld(){
    if (this.worldExists){
      this.theCanvas.clearPanel();
      System.out.println("draw the world");
      this.draw();
    }
  }

  /**
   * <P>User defined method to draw the <code>{@link World World}</code>.</P>
   * <P>Override this method in the game world class</P>
   */
  abstract public void draw();
  
  /**
   * Stop the world - temporarily or for good:
   * stop the timer, remove key listener, mark the world stopped.
   */
  protected void stopWorld(){
    this.worldExists = false;
    this.worldStopped = true;
    this.mytime.timer.stop();
    
    // discard the old key listener
    this.theCanvas.painter.removeKeyListener(ka);
  }
}

/**
 * The action listener for the timer events.
 * 
 * @author Viera K. Proulx
 * @since August 2, 2007
 */
class MyTimer{

  /** the current <code>{@link World World}</code> 
   * that handles the timer events */
  protected World currentWorld;

  /** the <code>Timer</code> that generates the time events */
  protected Timer timer;
  
  public boolean running = true;

  /** the timer speed */
  protected int speed;

  /**
   * Create the initial timer for the given 
   * <code>{@link World World}</code> at the given <code>speed</code>.
   * 
   * @param currentWorld the given <code>{@link World World}</code>
   * @param speed the given <code>speed</code>
   */
  protected MyTimer(World currentWorld, double speed){
    this.currentWorld = currentWorld;
    this.timer = new Timer((new Double(speed * 1000)).intValue(), 
        this.timerTasks);
    this.speed = (new Double(speed * 1000)).intValue();
  }

  /**
   * The callback for the timer events
   */
  protected ActionListener timerTasks = new ActionListener() {
    public void actionPerformed(ActionEvent evt) {
      if (running)
        currentWorld.processTick();
    }
  };

  /**
   * A helper method to convert the <code>speed</code> given as 
   * a delay time into milliseconds
   */
  protected void setSpeed(){ 
    this.timer.setDelay(this.speed);
  }
}



